﻿using KBEngine.Common;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace KBEngine.Extension
{
    public interface ITelnet
    {
        void Received(string msg);
    }
    public class TelnetConsole
    {
        enum Verbs
        {
            WILL = 251,
            WONT = 252,
            DO = 253,
            DONT = 254,
            IAC = 255
        }

        enum Options
        {
            SGA = 3
        }

        private ITelnet _client;
        private IPAddress _host;
        private ulong _port;
        protected TcpClient _telnet;
        protected ConcurrentQueue<string> _waitSend = new ConcurrentQueue<string>();
        protected string _fireReceive;
        public TelnetConsole(ITelnet wsInst, IPAddress host, ulong port)
        {
            _client = wsInst;
            _host = host;
            _port = port;
            _fireReceive = $"__{nameof(TelnetConsole)}_{nameof(__FireReceive)}_{Define.getObjAddres(this)}";
        }

        public void Close()
        {
            Event.deregisterIn(_fireReceive, this, nameof(__FireReceive));
            if (_telnet != null)
                _telnet.Close();
            _telnet = null;
        }

        public void Run()
        {
            Event.registerIn(_fireReceive, this, nameof(__FireReceive));
            _telnet = new TcpClient();
            _telnet.BeginConnect(_host, (int)_port, AsyncCallback, _telnet);
        }
        void AsyncCallback(IAsyncResult ar)
        {
            TcpClient tcpClient = null;
            try
            {
                tcpClient = (TcpClient)ar.AsyncState;
                tcpClient.EndConnect(ar);
                Console.WriteLine("连接服务器成功");
                onConnectedToConsole();

                NetworkStream networkStream = tcpClient.GetStream();
                Task t1 = Task.Run(async () =>
                {
                    while (true)
                    {
                        try
                        {
                            if (!networkStream.CanWrite)
                                continue;
                            if (!_waitSend.TryDequeue(out var input))
                                continue;

                            Console.WriteLine("发送到服务器:" + input);
                            var buffer = Encoding.Default.GetBytes(input);
                            await networkStream.WriteAsync(buffer, 0, buffer.Length);
                            await networkStream.FlushAsync();
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine("连接服务器异常" + ex.Message);
                            break;
                        }
                    }
                });
                Task t2 = Task.Run(() =>
                {
                    while (true)
                    {
                        try
                        {
                            if (!networkStream.CanRead)
                                continue;

                            List<byte> list = new List<byte>();
                            bool isCtrCode = false; //读取到Telnet控制码   https://www.iteye.com/blog/acboy025-1872162
                            do
                            {
                                int input = networkStream.ReadByte();
                                Console.WriteLine(input);
                                do
                                {
                                    if (input == -1)
                                        break;
                                    else if (input == (int)Verbs.IAC)
                                    {
                                        // interpret as command
                                        int inputverb = networkStream.ReadByte();
                                        if (inputverb == -1) break;
                                        switch (inputverb)
                                        {
                                            case (int)Verbs.IAC:
                                                //literal IAC = 255 escaped, so append char 255 to string
                                                list.Add((byte)inputverb);
                                                break;
                                            case (int)Verbs.DO:
                                            case (int)Verbs.DONT:
                                            case (int)Verbs.WILL:
                                            case (int)Verbs.WONT:
                                                // reply to all commands with "WONT", unless it is SGA (suppres go ahead)
                                                int inputoption = networkStream.ReadByte();
                                                if (inputoption == -1) break;
                                                networkStream.WriteByte((byte)Verbs.IAC);
                                                if (inputoption == (int)Options.SGA)
                                                    networkStream.WriteByte(inputverb == (int)Verbs.DO ? (byte)Verbs.WILL : (byte)Verbs.DO);
                                                else
                                                    networkStream.WriteByte(inputverb == (int)Verbs.DO ? (byte)Verbs.WONT : (byte)Verbs.DONT);
                                                networkStream.WriteByte((byte)inputoption);
                                                break;
                                            default:
                                                break;
                                        }
                                        break;
                                    }
                                    else if (input == 27) //'ESC'控制码开始标识
                                    {
                                        isCtrCode = true;
                                    }
                                    else if (input == 'm')//'m'控制码结束标识
                                    {
                                        isCtrCode = false;
                                    }
                                    else
                                    {
                                        if (isCtrCode)
                                            break;
                                        list.Add((byte)input);
                                    }
                                } while (false);
                            } while (networkStream.DataAvailable);
                            string msg = Encoding.Default.GetString(list.ToArray());
                            msg = msg.TrimEnd('\0', '\r', '\n');
                            if (!onReceivedConsoleData(msg))
                            {
                                Close();
                                break;
                            }
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine("连接服务器异常" + ex.Message);
                            break;
                        }
                    }
                });    
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message + ",10秒后重新连接");
                Close();
            }
        }
      

        /// <summary>
        /// 当成功连接上telnet控制台时回调
        /// </summary>
        protected virtual void onConnectedToConsole()
        { 
        }
        /// <summary>
        /// 当从telenet控制台收到了新数据以后回调
        /// </summary>
        protected virtual bool onReceivedConsoleData(string msg)
        {
            Event.fireIn(_fireReceive, msg);
            return true;
        }
        public void __FireReceive(string msg)
        {
            _client.Received(msg);
        }

        /// <summary>
        /// 当从客户端收到了新数据以后回调
        /// </summary>
        protected virtual bool onReceivedClientData(string data)
        {
            if (data == ":quit")
            {
                Close();
                return false;
            }
            if (!_telnet.Connected) return false;
            _waitSend.Enqueue(_pre_process_cmd(data));
            return true;
        }
        protected string _pre_process_cmd(string cmd)
        {
            if (cmd.EndsWith("\r\n"))
                return cmd;
            else if (cmd[cmd.Length - 1] == '\r')
                cmd += "\n";
            else if (cmd[cmd.Length - 1] == '\n')
                cmd = cmd.Substring(0, cmd.Length - 2) + "\r\n";
            else
                cmd += "\r\n";

            return cmd;
        }

        public void SendMsg(string data)
        {
            onReceivedClientData(data);
        }
    }
}
